package gateway


import (
	"bytes"
	"gitee.com/wayjin/fabric-extension/cryptoutil"
	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric/common/tools/protolator"
	protocommon "github.com/hyperledger/fabric/protos/common"
	protopeer "github.com/hyperledger/fabric/protos/peer"
	protoutils "github.com/hyperledger/fabric/protos/utils"
	"github.com/pkg/errors"
	cb "github.com/hyperledger/fabric/protos/common"
	putils "github.com/hyperledger/fabric/protos/utils"
	"io"
	"reflect"
)

func createSignedEnvelopeWithTLSBinding(txType cb.HeaderType, channelID string /*signer cc.LocalSigner*/, signer cryptoutil.Signer, dataMsg proto.Message, msgVersion int32, epoch uint64, tlsCertHash []byte) (*cb.Envelope, error) {
	payloadChannelHeader := putils.MakeChannelHeader(txType, msgVersion, channelID, epoch)
	payloadChannelHeader.TlsCertHash = tlsCertHash
	var err error
	payloadSignatureHeader := &cb.SignatureHeader{}
	if signer != nil {
		payloadSignatureHeader, err = signer.NewSignatureHeader()
		if err != nil {
			return nil, err
		}
	}
	data, err := proto.Marshal(dataMsg)
	if err != nil {
		//fmt.Println("marshal error")
		return nil, err
	}

	payloadBytes := putils.MarshalOrPanic(&cb.Payload{
		Header: putils.MakePayloadHeader(payloadChannelHeader, payloadSignatureHeader),
		Data:   data,
	})
	var sig []byte
	if signer != nil {
		sig, err = signer.Sign(payloadBytes)
		if err != nil {
			return nil, err
		}
	}

	env := &cb.Envelope{
		Payload:   payloadBytes,
		Signature: sig,
	}
	return env, nil
}


func createSignedTx(proposal *protopeer.Proposal, signer cryptoutil.Signer, resps ...*protopeer.ProposalResponse) (*protocommon.Envelope, error) {
	if len(resps) == 0 {
		return nil, errors.New("at least one proposal response is required")
	}

	hdr, err := protoutils.GetHeader(proposal.Header)
	if err != nil {
		return nil, errors.Wrap(err, "get header")
	}

	payload, err := protoutils.GetChaincodeProposalPayload(proposal.Payload)
	if err != nil {
		return nil, errors.Wrap(err, "get chaincode proposal payload")
	}
	signedBytes, err := signer.Serialize()
	if err != nil {
		return nil, errors.Wrap(err, "get creator")
	}
	shdr, err := protoutils.GetSignatureHeader(hdr.SignatureHeader)
	if err != nil {
		return nil, errors.Wrap(err, "get sign header")
	}
	if bytes.Compare(signedBytes, shdr.Creator) != 0 {
		return nil, errors.New("signer must be the same as the one referenced in the header")
	}

	hdrExt, err := protoutils.GetChaincodeHeaderExtension(hdr)
	if err != nil {
		return nil, err
	}

	// ensure that all actions are bitwise equal and that they are successful
	var a1 []byte
	for n, r := range resps {
		if n == 0 {
			a1 = r.Payload
			if r.Response.Status < 200 || r.Response.Status >= 400 {
				return nil, errors.Errorf("proposal response was not successful, error code %d, msg %s", r.Response.Status, r.Response.Message)
			}
			continue
		}

		if bytes.Compare(a1, r.Payload) != 0 {
			return nil, errors.New("ProposalResponsePayloads do not match")
		}
	}

	// fill endorsements
	endorsements := make([]*protopeer.Endorsement, len(resps))
	for n, r := range resps {
		endorsements[n] = r.Endorsement
	}

	// create ChaincodeEndorsedAction
	cea := &protopeer.ChaincodeEndorsedAction{ProposalResponsePayload: resps[0].Payload, Endorsements: endorsements}

	// obtain the bytes of the proposal payload that will go to the transaction
	propPayloadBytes, err := protoutils.GetBytesProposalPayloadForTx(payload, hdrExt.PayloadVisibility)
	if err != nil {
		return nil, err
	}

	// serialize the chaincode action payload
	cap := &protopeer.ChaincodeActionPayload{ChaincodeProposalPayload: propPayloadBytes, Action: cea}
	capBytes, err := protoutils.GetBytesChaincodeActionPayload(cap)
	if err != nil {
		return nil, err
	}

	// create a transaction
	taa := &protopeer.TransactionAction{Header: hdr.SignatureHeader, Payload: capBytes}
	taas := make([]*protopeer.TransactionAction, 1)
	taas[0] = taa
	tx := &protopeer.Transaction{Actions: taas}

	// serialize the tx
	txBytes, err := protoutils.GetBytesTransaction(tx)
	if err != nil {
		return nil, err
	}

	// create the payload
	payl := &protocommon.Payload{Header: hdr, Data: txBytes}
	paylBytes, err := protoutils.GetBytesPayload(payl)
	if err != nil {
		return nil, err
	}

	// sign the payload
	sig, err := signer.Sign(paylBytes)
	if err != nil {
		return nil, err
	}

	// here's the envelope
	return &protocommon.Envelope{Payload: paylBytes, Signature: sig}, nil
}

func createSignedEnvelop(txType cb.HeaderType, channelID string, signer cryptoutil.Signer, dataMsg proto.Message, msgVersion int32, epoch uint64) (*cb.Envelope, error) {
	return createSignedEnvelopeWithTLSBinding(txType, channelID, signer, dataMsg, msgVersion, epoch, nil)
}

func decodeBlockToJSON(msgName string, blockBytes []byte, output io.Writer) error {
	msgType := proto.MessageType(msgName)
	if msgType == nil {
		return errors.Errorf("message of type %s unknown", msgType)
	}
	msg := reflect.New(msgType.Elem()).Interface().(proto.Message)
	err := proto.Unmarshal(blockBytes, msg)
	if err != nil {
		return err
	}

	return protolator.DeepMarshalJSON(output, msg)

}